#include "ClientThread.h"

// Qt Includes
#include <QTcpSocket>
#include <QReadWriteLock>
#include <QSettings>
#include <QTimer>

// Library Includes
#include "TeaMakerMsg.h"
#include "TeaMakerPingMsg.h"

// ------------------------------------------------------------
// Construction & Destruction
// ------------------------------------------------------------
CClientThread::CClientThread(QObject *const parent/* = NULL*/)
	: QThread(parent),
	  m_nDataSize(0)
{
	m_pTcpSocket = new QTcpSocket(this);
	m_pStateLock = new QReadWriteLock;
	m_pTickTimer = new QTimer(this);

	setCurrentState(IdleState);

	connect(m_pTcpSocket, SIGNAL(readyRead()), this, SLOT(processIncomingData()));
	connect(m_pTcpSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
	
	connect(this, SIGNAL(socketDescriptorSet(int)), SLOT(setSocketDescriptorThreadSafe(int)), Qt::QueuedConnection);
	connect(this, SIGNAL(finished()), SLOT(deleteLater()));
	
	// make sure that every time we emit newState signal, stateChanged signal gets emitted too!
	connect(this, SIGNAL(newState(ClientThreadState)), this, SIGNAL(stateChanged()));

	connect(m_pTickTimer, SIGNAL(timeout()), this, SLOT(tickThread()));

	QSettings settings;
	int nThreadTickTimeout = settings.value("Threads/ThreadTickTimeout").toInt();

	m_pTickTimer->start(nThreadTickTimeout);
}

CClientThread::~CClientThread()
{
	delete m_pStateLock;
}

// ------------------------------------------------------------
// Implementation
// ------------------------------------------------------------
void CClientThread::run()
{
	exec();
}

// ------------------------------------------------------------
// Public Slots
// ------------------------------------------------------------
void CClientThread::setSocketDescriptor(int nSocketDescriptor)
{
	emit socketDescriptorSet(nSocketDescriptor);
}

// ------------------------------------------------------------
// Member Functions
// ------------------------------------------------------------
CClientThread::ClientThreadState CClientThread::getThreadState() const
{
	ClientThreadState currentState;

	m_pStateLock->lockForRead();
		currentState = m_CurrentState;
	m_pStateLock->unlock();

	return currentState;
}

void CClientThread::setCurrentState(ClientThreadState nState)
{
	m_pStateLock->lockForWrite();
		emit newState(nState);
		m_CurrentState = nState;
	m_pStateLock->unlock();
}

// ------------------------------------------------------------
// Private Slots
// ------------------------------------------------------------
void CClientThread::setSocketDescriptorThreadSafe(int nSocketDescriptor)
{
	qWarning("Received socket descriptor %d", nSocketDescriptor);

	if(m_pTcpSocket->state() != QAbstractSocket::UnconnectedState)
	{
		qWarning("Cannot setSocketDescriptor if socket-state is anything other than unconnected.");
		return;
	}

	if(!m_pTcpSocket->setSocketDescriptor(nSocketDescriptor))
	{
		qWarning("Failed to setSocketDescriptor.");
		return;
	}
	
	setCurrentState(ConnectedState);
	m_nDataSize = 0;
}

void CClientThread::processIncomingData()
{
	// Wrap the byte array in a QDataStream.
	QDataStream dataStream(m_pTcpSocket);

	while(true)
	{
		// If we don't already know the data-size, we want to see if we can obtain it.
		if(m_nDataSize == 0)
		{
			// We can obtain the data-size only if we have enough bytes to represent it.
			if((quint32)m_pTcpSocket->bytesAvailable() < sizeof(quint32))
				break;

			dataStream >> m_nDataSize;
		}

		// Now we know the data-size, we can keep waiting until the entire buffer is available.
		if((quint32)m_pTcpSocket->bytesAvailable() < m_nDataSize)
			break;

		// We have a complete message, so we can pass it on for parsing.
		CTeaMakerMsg *const pTeaMakerMsg = CTeaMakerMsg::getMessageFromDataStream(dataStream);
		if(pTeaMakerMsg)
			m_qMessagesIn.enqueue(pTeaMakerMsg);

		// We are finished with the current message, so we know longer know the size of the next
		// set of data.
		m_nDataSize = 0;
	}
}

void CClientThread::socketDisconnected()
{
	setCurrentState(IdleState);
}

void CClientThread::tickThread()
{
	// Process incoming messages.
	if(m_qMessagesIn.count() > 0)
	{
		CTeaMakerMsg *const pTeaMakerMsg = m_qMessagesIn.dequeue();

		if(pTeaMakerMsg)
		{
			switch(pTeaMakerMsg->getMessageType())
			{
			case CTeaMakerMsg::PingMessage:
				{
					qDebug() << "Ping Message";

					CTeaMakerPingMsg *const pTeaMakerPingMsg = CTeaMakerMsg::createPingMessage();

					if(pTeaMakerPingMsg)
						m_qMessagesOut.enqueue(pTeaMakerPingMsg);

					break;
				}

			default:
				qDebug() << "Unknown Message";
				break;
			}

			delete pTeaMakerMsg;
		}
	}

	// Process outgoing messages.
	if(m_qMessagesOut.count() > 0)
	{
		CTeaMakerMsg *const pTeaMakerMsg = m_qMessagesOut.dequeue();

		if(pTeaMakerMsg)
		{
			QDataStream dataStream(m_pTcpSocket);

			qDebug() << "Sending Message: " << pTeaMakerMsg->getMessageType();

			pTeaMakerMsg->serialiseMessage(dataStream, true);

			delete pTeaMakerMsg;
		}		
	}
}
